home *** CD-ROM | disk | FTP | other *** search
/ The 640 MEG Shareware Studio 2 / The 640 Meg Shareware Studio CD-ROM Volume II (Data Express)(1993).ISO / basic / bastips2.zip / BASICMEM.TXT < prev    next >
Text File  |  1986-07-02  |  18KB  |  355 lines

  1.                      Extra Memory for BASIC
  2.          (PC Magazine Vol 5 No 8 Apr 29, 1986 PC Tutor)
  3.  
  4.      A programmer is trying to develop a multipage screen-buffer
  5. technique for a monochrome display that will provide a function
  6. similar to the color adapter's ability to keep several different
  7. screen images stored in memory and flip them to the screen when
  8. appropriate.  Using an assembler routine, he is unable to use the
  9. highest 12K when running with the BASIC interpreter.  However, when
  10. the same program is run in compiled form (with appropriate changes
  11. such as CALL ABSOLUTE), the machine goes beserk.  Is there a way to
  12. do this using the memory-management DOS function calls (48h, 49h,
  13. and 4Ah) in DOS 2.1?
  14.  
  15.      Editor's Response:
  16.  
  17.      Programs compiled under the old IBM and Microsoft BASIC compilers
  18. use all available memory from the beginning of the program upward.
  19. Since these compilers were designed to work under DOS 1.1, they do not
  20. free up any of this program memory using function call 4Ah.  Conse-
  21. quently, a function call 48h from within an assembly language routine
  22. will not allocate a memory block since there is no memory left to
  23. allocate.  Nor could you first deallocate some of the compiled BASIC's
  24. memory with an assembler subroutine using function call 4Ah, because
  25. the program has already set itself up in the memory and expects it all
  26. to be usable.  There are a couple of solutions to the problem, however.
  27.      Under the BASIC compiler you could define a string variable 4000
  28. bytes long to hold the monochrome display contents with:
  29.  
  30. MONOHOLD$ = STRING$(4000,0)
  31.  
  32. You could then use VARPTR to pass the address of this string to your
  33. assembly language subroutine.  (Note that the first 2 bytes pointed
  34. to with VARPTR(MONOHOLD$) contain the length of the string, which is
  35. 4000.)  However, this would not work under the BASIC interpreter,
  36. whose strings are limited to 255 bytes.
  37.      If you'd like to keep the compiled and interpreted BASIC programs
  38. consistent in the way they store data in upper memory, therefore, you
  39. need to prevent the compiled BASIC program from taking up all of
  40. available memory.  This can be done by modifying some of the header
  41. information in the compiled .EXE run file, though, as you'll see there
  42. may be some problems in this approach.
  43.      Every .EXE file begins with header information DOS needs to load
  44. the program into memory, perform segment fix-ups, and allocate space
  45. for it to run.  This header information is documented in the DOS
  46. manuals for versions 2.0 and earlier and in the DOS Technical Reference
  47. manuals for versions 2.1 and later.
  48.      You can't see this header information if you load the .EXE file
  49. directly into DEBUG, because DEBUG uses the header to perform all the
  50. space allocation and fix-ups, making the program all ready to run.
  51. You'll have to first rename the file to an extension other than .EXE
  52. and then load it into DEBUG.  (But then you won't be able to run it
  53. in DEBUG.)
  54.  
  55.  
  56.      Use the following commands to look at the first part of an .EXE
  57. file header:
  58.  
  59. RENAME runfile.EXE runfile.XXX
  60. DEBUG
  61. -N runfile.XXX
  62. -L 0
  63. -D 0
  64.  
  65. The 2-byte word beginning at offset 000Ah is defined in the DOS
  66. Technical Reference manual as the "minimum number of 16-byte paragraphs
  67. required above the end of the loaded program."  This word will often
  68. be at offset 0000h.  The 2-byte word at offset 000Ch is the "maximum
  69. number of 16-byte paragraphs required above the end of the loaded
  70. program."  This will usually be FFFFh, which means that the program
  71. wants all available memory above where it loads.
  72.      This extra memory space is used for the "heap" and the "stack."
  73. During calculations, for instance, the stack is used to store inter-
  74. mediate results.  The heap is used by the program mostly for dynamic
  75. storage.  If your program executes a STRING$ command such as shown
  76. above, or if it DIMensions an array, the result has to go somewhere.
  77. It goes in the heap.  In a program that does a lot of dynamic string
  78. and array allocation, the heap can get pretty cluttered up and
  79. disorganized.  At times, normal execution can grind to a halt while
  80. the program cleans up the heap in a process technically referred to
  81. as "garbage collection."
  82.      Of course, most compiled BASIC programs don't really need all
  83. available memory and can function normally with less.  Thus, you can
  84. change the "maximum memory" word at offset 000Ch in the .EXE header
  85. to something lower than FFFFh to prevent DOS from allocating all
  86. available memory to the program.
  87.      Let's assume, for example, that you have 256K memory in your
  88. machine and that the bottom 128K memory is taken up by DOS and
  89. resident programs.  (You get this second figure by subtracting the
  90. "bytes free" from the "bytes total memory" information provided by
  91. CHKDSK.)  If your .EXE program is about 32K, you can give it another
  92. 64K when it runs and still have 32K left over at the top of memory
  93. for your screen swaps.
  94.      To do this, you'd enter the following DEBUG commands as a direct
  95. continuation of the program above (whose last line was:  -D 0):
  96.  
  97. -E 000C 00 10
  98. -W
  99. -Q
  100. RENAME runfile.XXX runfile.EXE
  101.  
  102. Note that the 2 bytes (00 and 10) specified after the E (Enter) command
  103. are in reverse order, as usual.  The actual word you've entered is
  104. 1000h or 4096 decimal.  This is the number of 16-byte paragraphs, and
  105. it corresponds to 64K bytes.
  106.      When you now run your .EXE program, the DOS loaders uses this
  107. "maximum memory" word in the .EXE header to computer a "top of memory"
  108. word (also in paragraph form) that it puts in offset 0002h of the
  109. programs Program Segment Prefix.  The compiled BASIC prologue uses
  110. this value to limit the amount of memory it uses.
  111.      You can look at the Program Segment Prefix by loading the .EXE
  112. file (not the renamed .XXX file) into DEBUG and executing the command:
  113.  
  114. -D 0
  115.  
  116. If an .EXE header has FFFFh in offset 000Ch, the Program Segment Prefix
  117. top of memory word at 0002h will be 4000h for a 256K machine and A000h
  118. for a 640K machine (again, you'll see the 2 bytes shown in reverse
  119. order).  If the amount of real available memory exceeds the length of
  120. the program plus the additional memory (plus the memory taken up by
  121. DEBUG), then you'll see a lower value.  If you have enough high memory
  122. for screen swaps while in DEBUG, you'll certainly have enough when
  123. running the program outside of DEBUG.
  124.      How low can you set the value at 000Ch in the .EXE header?
  125. Definitely don't set it to 0 -- that indicates to DOS that the program
  126. is to be loaded high in memory, and it will crash.  With some compiled
  127. BASIC programs, it can be set as low as 0060h paragraphs (about 1.5K
  128. bytes).  If you have a lot of dynamic string and array storage in your
  129. program, you'll need something higher, or you'll probably get an "out
  130. of memory" error during program execution.
  131.      Note also that the program may run slower, since it has to perform
  132. more frequent garbage collections to clean up the heap. You'll probably
  133. have to experiment to get an optimum value.
  134.      If you do your compilation and linking using a batch file with a
  135. replaceable parameter for the filename, you can automate this .EXE
  136. header modification with the following batch file code after the LINK:
  137.  
  138. RENAME %1.EXE %1.XXX
  139. DEBUG %1.XXX < HEADCHG
  140. RENAME %1.XXX %2.EXE
  141.  
  142. where HEADCHG is a file containing the DEBUG commands:
  143.  
  144. E 010C 00 10
  145. W
  146. Q
  147.  
  148. Note here that you have to use 0100h plus the header offset for the
  149. E (Enter) command because DEBUG is loading the file at offset 0100h.
  150.      The .EXE file header also contains a word "checksum" value, which
  151. is supposed to be used by the DOS loader to ensure .EXE file integrity.
  152. If we change a value in the header, then the checksum will not check.
  153. However, DOS doesn't use this checksum when loading programs.  If it
  154. does in the future, you'll have to make an adjustment to that value as
  155. well.
  156.      You may also be interested to learn that the LINK program included
  157. with the Microsoft (but not the IBM) Macro Assembler and the Microsoft
  158. C Compiler includes a switch called CPARMAXALLOC to set the maximum
  159. memory value during the LINK so you don't have to adjust it later.
  160. The next version of the Microsoft C Compiler also has an EXEMOD program
  161. to change this header information with a one-line command.
  162.      (Persons tired of having COMMAND.COM reload after running a
  163. compiled BASIC program may alter this word to prevent the program from
  164. overwriting the transient part of COMMAND.COM at high memory.)
  165.  
  166.     Although there's nothing wrong with modifying this part of the .EXE
  167. header, you may have problems using memory above the loaded program.
  168.      First, the value you pick to go in the .EXE header will probably
  169. be dependent on your machine's memory configuration.  If it's run on a
  170. machine with a few more resident programs loaded, you may not have
  171. enough high memory to store you screen swaps.
  172.      Second, in a multitasking system such as TopView, this solution
  173. will be absolutely disastrous, because your program is using memory
  174. that doesn't belong to it.  Only memory up to that top of memory word
  175. in the Program Segment Prefix is allocated for your program, and you
  176. are deliberately using memory higher than that.  TopView may be using
  177. this memory for some other program.  The same problem exists if you
  178. use high memory in the BASICA interpreter.
  179.      You also don't want to write over the first few bytes right after
  180. the top of the extra memory, because these contain a marker DOS uses
  181. for memory allocation.
  182.      There is another solution, but it would require using a separate
  183. assembly language program to allocate memory below your loaded program.
  184. This assembly language program would perform the following four steps:
  185.      1) deallocate all memory above its code by executing the DOS 4Ah
  186. function call;
  187.      2) allocate a block of memory for screen swaps (or BLOADs or
  188. whatever) with function call 48h;
  189.      3) store the address of this block in lower memory, probably in
  190. one of the user interrupt vectors beginning at 0000:0180h; and,
  191.      4) load the compiled BASIC program (or the BASICA interpreter)
  192. using the EXEC function call 4Bh.
  193.      The BASIC program -- in either interpreted or compiled form --
  194. could retrieve the address of this allocated memory block using PEEK
  195. and pass this address to the assembly language subroutine.  When the
  196. BASIC program terminates, control would pass back to the assembler
  197. program that loaded it, which would then terminate.
  198.  
  199.  
  200.  
  201. -----------------------------------------------------------------
  202.                     Minding Memory From BASIC
  203.         (COMPUTE! Magazine June 1986 by D. W. Neuendorf)
  204.  
  205.      Memory management in DOS has become an important issue.  The new
  206. desktop tools and coresident programs are designed to wait in the
  207. background to be called during the operation of another program.  A
  208. number of these utilities may be lurking in memory at once, and
  209. programmers can't predict which other programs will be present with
  210. their own.  The result can be memory conflicts and system crashes.
  211.      DOS 2.0 and later versions contain several function calls designed
  212. to give the operating system control over how the computer's memory is
  213. divided among programs residing in memory simultaneously.  The most
  214. basic of these functions simply attempt to allocate and deallocate
  215. blocks of memory at a program's request.  These DOS calls are readily
  216. available to machine language programmers, just like all other machine-
  217. level resources.
  218.      BASIC programmers, on the other hand, have no direct access to
  219. many DOS functions.  But there are ways for BASIC programs to call on
  220. DOS to perform these memory management tasks.
  221.      There are two DOS functions we're interested in -- one for
  222. allocating memory and another for deallocating memory.
  223.      In machine language, both functions are called by placing a
  224. function number in the microprocessor's AH register and calling
  225. Interrupt 21h.  (Function numbers indicated to DOS which function is
  226. being called.  The interrupt then performs the function.)  The numbers
  227. are 48h for the allocate function and 49h for the deallocate function.
  228.      In addition to these numbers, each function call requires that
  229. you pass an argument.  The allocate function requires the number of
  230. 16-byte paragraphs of memory to be allocated.  This number must be
  231. placed in the microprocessor's BX register.  The deallocate function
  232. requires the segment address of a block to be deallocated.  This
  233. number must be placed in the ES register.
  234.      After each function is performed, it returns a value.  The
  235. allocate function returns, via the AX register, either the segment
  236. address of an allocated block or an error code (7 or 8 plus a set
  237. carry bit) if the function was unsuccessful.  The deallocation routine
  238. returns nothing if successful, but sets the carry bit and returns an
  239. error code (7 or 9) if unsuccessful.  The machine language listings
  240. below show the assembler code necessary to call these functions.
  241.      The BASIC program shows how to call these functions from BASIC.
  242. Since the allocate routine is not available initially and therefore
  243. can't allocate space for itself, the program reserves a few bytes for
  244. it just above BASIC (using the CLEAR statement in line 10).  Once the
  245. allocate routine has been installed (lines 40-60), it can be used to
  246. get memory from DOS for machine language routines and other data.  An
  247. example of its use is the call in line 70, which gets the segment
  248. address of a memory block for the deallocate routine.  Finally, line
  249. 120 shows an example of using the deallocate routine -- it deallocates
  250. its own memory.
  251.      After studying the BASIC program, you'll see that it's possible
  252. to put a machine language subroutine outside BASIC's 64K memory area,
  253. thus saving some space for BASIC programs.  Better yet, you don't have
  254. to worry about where in memory you're hiding the routine -- DOS takes
  255. care of it.  If you use a lot of machine language subroutines or
  256. store large amounts of data in memory, you'll have a lot more room to
  257. work with if you don't have to put everything inside BASIC's own
  258. segment.
  259.      If everyone relies on DOS to determine where their programs reside
  260. in memory, we can all feel confident that our coresident programs are
  261. not overlapping and conflicting with each other.  But if too many
  262. programmers bypass these DOS functions, the rest of us won't dare to
  263. rely on them, either.  After all, DOS can protect only the data or
  264. programs that it knows about.
  265.  
  266.  
  267.  
  268. DOS Memory Allocation:
  269.  
  270.                  page 50,132
  271. 0000             alloc segment para
  272.                      assume cs:alloc
  273.                      assume ds:alloc
  274.                      assume es:alloc
  275. 0000             allocate proc far
  276.                  ;
  277.                  ;Routine to allow BASIC to make DOS call to allocate
  278.                  ;a block of memory outside of BASIC's own segment.
  279.                  ;CALL ALLOC(MEMORY) - when BASIC calls the routine,
  280.                  ;MEMORY contains the number of bytes to be allocated.
  281.                  ;When the routine returns to BASIC, MEMORY contains
  282.                  ;the segment address of the allocated block of memory.
  283.                  ;A 7 or 8 indicates allocation failed.
  284.                  ;
  285. 0000  55         push bp
  286. 0001  8B EC      mov bp,sp
  287. 0003  8B 5E 06   mov bx,[bp+6]  ;get address of MEMORY
  288. 0006  8B 1F      mov bx,[bx]    ;get number of bytes to be allocated
  289. 0008  B4 48      mov ah,48h     ;DOS function number
  290. 000A  CD 21      int 21h        ;DOS call itself
  291. 000C  8B 5E 06   mov bx,[bp+6]  ;address of MEMORY
  292. 000F  89 07      mov [bx],ax    ;put segment address of allocated
  293.                                  memory in MEMORY
  294. 0011  5D         pop bp
  295. 0012  CA 0002    ret 2
  296.                  ;
  297. 0015             allocate endp
  298. 0015             alloc ends
  299.                  end
  300.  
  301.  
  302. DOS Memory Deallocation:
  303.  
  304.                  page 50,132
  305. 0000             dealloc segment para
  306.                     assume cs:dealloc
  307.                     assume ds:dealloc
  308.                     assume es:dealloc
  309. 0000             dlc proc far
  310.                  ;
  311.                  ;Routine to allow BASIC to make DOS call to deallocate
  312.                  ;a block of memory previously allocated using ALLOC.
  313.                  ;CALL DEALLOC(MEMORY) - when BASIC calls the routine,
  314.                  ;MEMORY contains the segment address of the block of
  315.                  ;memory to be dealloc.  When the routine returns to
  316.                  ;BASIC, MEMORY contains either the original segment
  317.                  ;address or an error code.  A 7 or 9 indicates
  318.                  ;allocation failed.
  319.                  ;
  320. 0000  55         push bp
  321. 0001  06         push es
  322. 0002  8B EC      mov bp,sp
  323. 0004  8B 5E 06   mov bx,[bp+8]    ;get address of MEMORY
  324. 0007  8E 07      mov es,[bx]      ;get segment address of block to be
  325.                                    deallocated
  326. 0009  B4 49      mov ah,49h       ;DOS function number
  327. 000B  CD 21      int 21h          ;DOS call itself
  328. 000D  8B 5E 06   mov bx,[bp+8]
  329. 0010  89 07      mov [bx],ax      ;put error code in MEMORY
  330. 0012  07         pop es
  331. 0013  5D         pop bp
  332. 0014  CA 0002    ret 2
  333.                  ;
  334. 0017             dlc endp
  335. 0017             dealloc ends
  336.                  end
  337.  
  338.  
  339. DOS Memory Functions in BASIC:
  340.  
  341. 10 CLEAR ,&HFFDF    'Reserve a few bytes just above BASIC for alloc routine
  342. 20 DEFINT A-Z
  343. 30 DEF SEG:ALLOC=&HFFDF:DMEMORY=2:DEALLOC=0
  344. 40 RESTORE 50:FOR X=0 TO 20:READ Y:POKE X+ALLOC,Y:NEXT  'Install alloc.
  345. 50 DATA &h55,&h8b,&hec,&h8b,&h5e,&h06,&h8b,&h1f,&hb4,&h48,&hcd
  346. 60 DATA &h21,&h8b,&h5e,&h06,&h89,&h07,&h5d,&hca,&h02,&h0
  347. 70 CALL ALLOC(DMEMORY)  'DOS call to allocate memory for dealloc routine
  348. 80 DEF SEG=DMEMORY
  349. 90 RESTORE 100:FOR X=0 TO 22:READ Y:POKE X,Y:NEXT  'Install dealloc
  350. 100 DATA &h55,&h06,&h8b,&hec,&h8b,&h5e,&h08,&h8e,&h07,&hb4,&h49,&hcd
  351. 110 DATA &h21,&h8b,&h5e,&h08,&h89,&h07,&h07,&h5d,&hca,&h02,&h00
  352. 120 CALL DEALLOC(DMEMORY)
  353. 130 END
  354.  
  355.